package com.franklin.ideaplugin.api.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.api.beans.GenericClass;
import com.franklin.ideaplugin.api.beans.GenericParameter;
import com.franklin.ideaplugin.api.ui.editor.CustomEditor;
import com.franklin.ideaplugin.easytesting.notification.EasyTestingNotifier;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.SlowOperations;
import com.othelle.jtuples.Tuple2;
import com.othelle.jtuples.Tuples;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

/**
 * @author Ye Junhui
 * @since 2023/7/22
 */
public class JsonPathUtils {

    /**
     * 解析元素在JSON的层级字段
     * @param psiElement
     * @param customEditor
     * @return
     */
    @Nullable
    public static List<PsiUtil.FieldMethod> resolveCurrentPathFields(PsiElement psiElement, CustomEditor customEditor) {
        return SlowOperations.allowSlowOperations(() -> {
            //文本范围
            if (Objects.isNull(psiElement)) {
                return null;
            }
            String targetText = psiElement.getText();
            TextRange textRange = psiElement.getTextRange();
            String text = customEditor.getText();
            //找到当前编辑位置的JSON对象
            List<String> pathList = resolveCurrentTargetPath(textRange, text);

            PsiType psiType = customEditor.getPsiType();
            if (Objects.isNull(psiType)) {
                return null;
            }

            //dfs遍历路径
            return ExecuteUtils.<List<PsiUtil.FieldMethod>>ignoreErrorAction(
                    () -> dfs(psiElement.getProject(), psiType.getCanonicalText(),pathList,0),
                    () -> ExecuteUtils.ignoreErrorAction(
                            () -> dfs(psiElement.getProject(), PsiTypesUtil.getPsiClass(psiType).getQualifiedName(),pathList,0),
                            Collections.emptyList()
                    )
            );
        });
    }

    /**
     * dfs寻找字段
     * @param project
     * @param classQualifiedName
     * @param pathList
     * @param currentIndex
     * @return
     */
    private static List<PsiUtil.FieldMethod> dfs(Project project, String classQualifiedName, List<String> pathList, int currentIndex) {
        GenericClass genericClass = PsiUtil.resolveGenericClass(project, classQualifiedName);
        if (Objects.nonNull(genericClass)){
            //泛型类
            List<GenericParameter> genericParameterList = genericClass.getGenericParameterList();
            GenericParameter genericParameter = genericParameterList.get(genericParameterList.size() - 1);
            return dfs(project,genericParameter.getGenericClass().getQualifiedName(), pathList,currentIndex);
        }
        PsiClass psiClass = PsiUtil.findPsiClass(project, classQualifiedName);
        if (Objects.isNull(psiClass)){
            EasyTestingNotifier.notifyError(project,classQualifiedName);
            return Collections.emptyList();
        }
        List<PsiUtil.FieldMethod> fieldMethodList = PsiUtil.getFieldsMethod(psiClass);
        if (currentIndex >= pathList.size()){
            return fieldMethodList;
        }
        String targetKey = pathList.get(currentIndex);
        for (PsiUtil.FieldMethod fieldMethod : fieldMethodList) {
            if (fieldMethod.getFieldName().equals(targetKey)) {
                PsiField field = fieldMethod.getField();
                if (Objects.nonNull(field)) {
                    List<PsiUtil.FieldMethod> childList = ExecuteUtils.ignoreErrorAction(
                            () -> dfs(project, field.getType().getCanonicalText(), pathList, currentIndex + 1),
                            Collections.emptyList()
                    );
                    if (CollectionUtil.isNotEmpty(childList)){
                        return childList;
                    }
                }
                List<PsiMethod> fieldGetters = fieldMethod.getFieldGetters();
                if (CollectionUtil.isNotEmpty(fieldGetters)){
                    PsiMethod psiMethod = fieldGetters.get(0);
                    PsiType returnType = psiMethod.getReturnType();
                    return ExecuteUtils.ignoreErrorAction(
                            () -> dfs(project, returnType.getCanonicalText(), pathList, currentIndex + 1),
                            Collections.emptyList()
                    );
                }
            }
        }
        return Collections.emptyList();
    }

    /**
     * 解析当前对象的路径
     *
     * @param targetRange
     * @param text
     * @return
     */
    public static List<String> resolveCurrentTargetPath(TextRange targetRange, String text) {
        LinkedList<String> stack = Lists.newLinkedList();
        for (int i = targetRange.getStartOffset(); i >= 0; i--) {
            if (text.charAt(i) == '}') {
                //入栈
                stack.addLast("}");
            } else if (text.charAt(i) == '{') {
                //寻找上一个不为空的字符
                int index = findBeforeFirstNotBlankChar(text, i);
                if (index < 0){
                    continue;
                }
                i = index;
                char c = text.charAt(index);
                if (c == ','){
                    //","说明是数组，需要跳过前面元素
                    int preIndex = skipArrayPreElements(text, index - 1);
                    i = preIndex + 1;
                    continue;
                }

                String last = stack.pollLast();
                if (Objects.nonNull(last) && last.equals("}")) {
                    //不是当前对象跳过
                    continue;
                }
                //重新放回
                if (Objects.nonNull(last)) {
                    stack.addLast(last);
                }

                //往前寻找此对象的key
                if (i > 0) {
                    //寻找 右"
                    int r = -1;
                    for (int j = i - 1; j >= 0; j--) {
                        if (text.charAt(j) == '"') {
                            r = j;
                            break;
                        }
                    }
                    //寻找 左"
                    int l = -1;
                    if (r > 0) {
                        for (int j = r - 1; j >= 0; j--) {
                            if (text.charAt(j) == '"') {
                                l = j;
                                break;
                            }
                        }
                    }
                    if (l > 0 && r > 1) {
                        String key = text.substring(l + 1, r);
                        stack.addLast(key);
                        //跳过
                        i = l - 1;
                    }
                }
            }
        }

        //JSON路径结果
        List<String> depth = Lists.newArrayList();
        while (!stack.isEmpty()) {
            String last = stack.pollLast();
            depth.add(last);
        }
        return depth;
    }

    /**
     * 向前寻找第一个不为空的字符
     * @param text
     * @param fromIndex
     * @return
     */
    private static int findBeforeFirstNotBlankChar(String text,int fromIndex){
        for (int i = fromIndex - 1; i >= 0; i--) {
            char c = text.charAt(i);
            if (!CharUtil.isBlankChar(c)){
                return i;
            }
        }
        return -1;
    }

    /**
     * 跳过前面的元素
     * @param text
     * @param formIndex
     * @return
     */
    private static int skipArrayPreElements(String text,int formIndex){
        LinkedList<Character> stack = new LinkedList<>();
        for (int i = formIndex; i >= 0; i--) {
            char c = text.charAt(i);
            if (c == '}'){
                stack.addFirst(c);
            }else if (c == '{'){
                stack.pollFirst();
                if (stack.isEmpty()){
                    //栈为空，说明匹配成功
                    return i;
                }
            }
        }
        return -1;
    }
}
